{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "656ed516",
   "metadata": {},
   "source": [
    "# The Property Framework"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "1ac4c84b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed023bb1",
   "metadata": {},
   "source": [
    "Qiskit Nature 0.2.0 introduces the _Property_ framework. This framework replaces the legacy driver return types like `QMolecule` and `WatsonHamiltonian` with a more modular and extensible approach."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "022da759",
   "metadata": {},
   "source": [
    "In this tutorial, we will walk through the framework, explain its most important components and show you, how you can extend it with a custom _property_ yourself."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e95594d",
   "metadata": {},
   "source": [
    "## What is a `Property`?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d04769fd",
   "metadata": {},
   "source": [
    "At its core, a `Property` is an object complementing some raw data with functions that allow you to transform/manipulate/interpret this raw data. This \"definition\" is kept rather abstract on purpose, but what it means is essentially the following.\n",
    "A `Property`:\n",
    "\n",
    "* represents a physical observable (that's the raw data)\n",
    "* can be expressed as an operator\n",
    "* can be evaluated with a wavefunction\n",
    "* provides an `interpret` method which gives meaning to the eigenvalue of the evaluated qubit operator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "77fb4108",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.properties import Property, GroupedProperty"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8da122b1",
   "metadata": {},
   "source": [
    "The `qiskit_nature.properties` module provides two classes:\n",
    "\n",
    "1. `Property`: this is the basic interface. It requires only a `name` and an `interpret` method to be implemented.\n",
    "2. `GroupedProperty`: this class is an implementation of the [Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) which allows you to _group_ multiple properties into one.\n",
    "\n",
    "**Note:** Grouped properties must have unique `name` attributes!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3c2c15a",
   "metadata": {},
   "source": [
    "## Second Quantization Properties"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08c67dd6",
   "metadata": {},
   "source": [
    "At the time of writing, Qiskit Nature ships with a single variant of properties: the `SecondQuantizedProperty` objects.\n",
    "\n",
    "This sub-type adds one additional requirement because any `SecondQuantizedProperty`\n",
    "\n",
    "* **must** implement a `second_q_ops` method which constructs a (list of) `SecondQuantizedOp`s."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b30111ec",
   "metadata": {},
   "source": [
    "The `qiskit_nature.properties.second_quantization` module is further divided into `electronic` and `vibrational` modules (similar to other modules of Qiskit Nature).\n",
    "Let us dive into the `electronic` sub-module first."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2aa8e7b3",
   "metadata": {},
   "source": [
    "### Electronic Second Quantization Properties"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39393b42",
   "metadata": {},
   "source": [
    "Out-of-the-box Qiskit Nature ships the following electronic properties:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "11c080c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/oss/Files/Qiskit/.direnv/python-3.9.6/lib/python3.9/site-packages/pyscf/lib/misc.py:46: H5pyDeprecationWarning: Using default_file_mode other than 'r' is deprecated. Pass the mode to h5py.File() instead.\n",
      "  h5py.get_config().default_file_mode = 'a'\n"
     ]
    }
   ],
   "source": [
    "from qiskit_nature.properties.second_quantization.electronic import (\n",
    "    ElectronicEnergy,\n",
    "    ElectronicDipoleMoment,\n",
    "    ParticleNumber,\n",
    "    AngularMomentum,\n",
    "    Magnetization,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57ee71e0",
   "metadata": {},
   "source": [
    "`ElectronicEnergy` is arguably the most important property because it contains the _Hamiltonian_ representing the electronic structure problem whose eigenvalues we are interested in when solving an `ElectronicStructureProblem`.\n",
    "The way in which it stores this Hamiltonian is, just like the rest of the framework, highly modular. The initializer of the `ElectronicEnergy` has a single required argument, `electronic_integrals`, which is a `List[ElectronicIntegrals]`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87bf5b74",
   "metadata": {},
   "source": [
    "#### ElectronicIntegrals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3807ffbb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.properties.second_quantization.electronic.integrals import (\n",
    "    ElectronicIntegrals,\n",
    "    OneBodyElectronicIntegrals,\n",
    "    TwoBodyElectronicIntegrals,\n",
    "    IntegralProperty,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e2b1630",
   "metadata": {},
   "source": [
    "The `ElectronicIntegrals` are a container class for _n-body_ interactions in a given basis, which can be any of the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1e969daa",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.properties.second_quantization.electronic.bases import ElectronicBasis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "b3ead61a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<ElectronicBasis.AO: 'atomic'>,\n",
       " <ElectronicBasis.MO: 'molecular'>,\n",
       " <ElectronicBasis.SO: 'spin'>]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(ElectronicBasis)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37aae51a",
   "metadata": {},
   "source": [
    "Let us take the `OneBodyElectronicIntegrals` as an example. As the name suggests, this container is for 1-body interaction integrals. You can construct an instance of it like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "55fc757d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(MO) 1-Body Terms:\n",
      "\tAlpha\n",
      "\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t[0, 0] = 1.0\n",
      "\t[1, 1] = 1.0\n",
      "\tBeta\n",
      "\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t[0, 0] = 2.0\n",
      "\t[1, 1] = 2.0\n"
     ]
    }
   ],
   "source": [
    "one_body_ints = OneBodyElectronicIntegrals(\n",
    "    ElectronicBasis.MO,\n",
    "    (\n",
    "        np.eye(2),\n",
    "        2 * np.eye(2),\n",
    "    ),\n",
    ")\n",
    "print(one_body_ints)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7c1b39e",
   "metadata": {},
   "source": [
    "As you can see, the first argument simply specifies the basis of the integrals.\n",
    "The second argument requires a little further explanation:\n",
    "\n",
    "1. In the case of the `AO` or `MO` basis, this argument **must** be a pair of numpy arrays, where the first and second one are the alpha- and beta-spin integrals, respectively.\n",
    "\n",
    "**Note:** The second argument may be `None`, for the case where the beta-spin integrals are the same as the alpha-spin integrals (so there is no need to provide the same values twice).\n",
    "\n",
    "2. In the case of the `SO` basis, this argument **must** be a single numpy array, storing the alpha- and beta-spin integrals in blocked order, i.e. like so:\n",
    "```python\n",
    "spin_basis = np.block([[alpha_spin, zeros], [zeros, beta_spin]])\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1112025f",
   "metadata": {},
   "source": [
    "The `TwoBodyElectronicIntegrals` work pretty much the same except that they contain all possible spin-combinations in the tuple of numpy arrays. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "795e2fa2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(MO) 2-Body Terms:\n",
      "\tAlpha-Alpha\n",
      "\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t[0, 0, 0, 0] = 1.0\n",
      "\t[0, 0, 0, 1] = 2.0\n",
      "\t[0, 0, 1, 0] = 3.0\n",
      "\t[0, 0, 1, 1] = 4.0\n",
      "\t[0, 1, 0, 0] = 5.0\n",
      "\t... skipping 11 entries\n",
      "\tBeta-Alpha\n",
      "\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t[0, 0, 0, 0] = 16.0\n",
      "\t[0, 0, 0, 1] = 17.0\n",
      "\t[0, 0, 1, 0] = 18.0\n",
      "\t[0, 0, 1, 1] = 19.0\n",
      "\t[0, 1, 0, 0] = 20.0\n",
      "\t... skipping 11 entries\n",
      "\tBeta-Beta\n",
      "\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t[0, 0, 0, 0] = -16.0\n",
      "\t[0, 0, 0, 1] = -15.0\n",
      "\t[0, 0, 1, 0] = -14.0\n",
      "\t[0, 0, 1, 1] = -13.0\n",
      "\t[0, 1, 0, 0] = -12.0\n",
      "\t... skipping 11 entries\n",
      "\tAlpha-Beta\n",
      "\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t[0, 0, 0, 0] = 16.0\n",
      "\t[0, 0, 0, 1] = 24.0\n",
      "\t[0, 0, 1, 0] = 20.0\n",
      "\t[0, 0, 1, 1] = 28.0\n",
      "\t[0, 1, 0, 0] = 18.0\n",
      "\t... skipping 11 entries\n"
     ]
    }
   ],
   "source": [
    "two_body_ints = TwoBodyElectronicIntegrals(\n",
    "    ElectronicBasis.MO,\n",
    "    (\n",
    "        np.arange(1, 17).reshape((2, 2, 2, 2)),\n",
    "        np.arange(16, 32).reshape((2, 2, 2, 2)),\n",
    "        np.arange(-16, 0).reshape((2, 2, 2, 2)),\n",
    "        None,\n",
    "    ),\n",
    ")\n",
    "print(two_body_ints)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e05c29e",
   "metadata": {},
   "source": [
    "We should take note of a few observations:\n",
    "\n",
    "* the numpy arrays shall be ordered as `(\"alpha-alpha\", \"beta-alpha\", \"beta-beta\", \"alpha-beta\")`\n",
    "* the `alpha-alpha` matrix may **not** be `None`\n",
    "* if the `alpha-beta` matrix is `None`, but `beta-alpha` is not, its transpose will be used (like above)\n",
    "* in any other case, matrices which are `None` will be filled with the `alpha-alpha` matrix\n",
    "\n",
    "* in the `SO` basis case, a single numpy array must be specified. Refer to `TwoBodyElectronicIntegrals.to_spin()` for its exact formatting."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1775e606",
   "metadata": {},
   "source": [
    "#### ElectronicEnergy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43cc5fce",
   "metadata": {},
   "source": [
    "Now we are ready to construct an `ElectronicEnergy` instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fbd8d110",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ElectronicEnergy\n",
      "\t(MO) 1-Body Terms:\n",
      "\t\tAlpha\n",
      "\t\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t\t[0, 0] = 1.0\n",
      "\t\t[1, 1] = 1.0\n",
      "\t\tBeta\n",
      "\t\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t\t[0, 0] = 2.0\n",
      "\t\t[1, 1] = 2.0\n",
      "\t(MO) 2-Body Terms:\n",
      "\t\tAlpha-Alpha\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 1.0\n",
      "\t\t[0, 0, 0, 1] = 2.0\n",
      "\t\t[0, 0, 1, 0] = 3.0\n",
      "\t\t[0, 0, 1, 1] = 4.0\n",
      "\t\t[0, 1, 0, 0] = 5.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tBeta-Alpha\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 16.0\n",
      "\t\t[0, 0, 0, 1] = 17.0\n",
      "\t\t[0, 0, 1, 0] = 18.0\n",
      "\t\t[0, 0, 1, 1] = 19.0\n",
      "\t\t[0, 1, 0, 0] = 20.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tBeta-Beta\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = -16.0\n",
      "\t\t[0, 0, 0, 1] = -15.0\n",
      "\t\t[0, 0, 1, 0] = -14.0\n",
      "\t\t[0, 0, 1, 1] = -13.0\n",
      "\t\t[0, 1, 0, 0] = -12.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tAlpha-Beta\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 16.0\n",
      "\t\t[0, 0, 0, 1] = 24.0\n",
      "\t\t[0, 0, 1, 0] = 20.0\n",
      "\t\t[0, 0, 1, 1] = 28.0\n",
      "\t\t[0, 1, 0, 0] = 18.0\n",
      "\t\t... skipping 11 entries\n"
     ]
    }
   ],
   "source": [
    "electronic_energy = ElectronicEnergy(\n",
    "    [one_body_ints, two_body_ints],\n",
    ")\n",
    "print(electronic_energy)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0701589d",
   "metadata": {},
   "source": [
    "This property can now be used to construct a `SecondQuantizedOp` (which can then be mapped to a `QubitOperator`):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "80858cab",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fermionic Operator\n",
      "register length=4, number terms=20\n",
      "  (22+0j) * ( +_0 -_1 +_2 -_3 )\n",
      "+ (-26+0j) * ( +_0 -_1 -_2 +_3 )\n",
      "+ (30+0j) * ( +_0 -_1 +_3 -_3 )\n",
      "+ (18+0j) * ( +_0 -_1 +_2 -_2 )\n",
      "+ (-21+0j) * ( -_0 +_1 +_2 -_3 )\n",
      "+ (25+0j) * ( -_0 +_1 -_2 +_3 )\n",
      "+  ...\n"
     ]
    }
   ],
   "source": [
    "hamiltonian = electronic_energy.second_q_ops()[0]  # here, output length is always 1\n",
    "print(hamiltonian)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ab73154",
   "metadata": {},
   "source": [
    "#### Result interpretation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "357d2fdb",
   "metadata": {},
   "source": [
    "An additional benefit which we gain from the `Property` framework, is that the result interpretation of a computed eigenvalue can be handled by each property itself. This results in nice and logically consistent classes because the result gets interpreted in the same context where the raw data is available."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "d66678cd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== GROUND STATE ENERGY ===\n",
      " \n",
      "* Electronic ground state energy (Hartree): -1\n",
      "  - computed part:      -1\n"
     ]
    }
   ],
   "source": [
    "from qiskit_nature.results import ElectronicStructureResult\n",
    "\n",
    "# some dummy result\n",
    "result = ElectronicStructureResult()\n",
    "result.eigenenergies = np.asarray([-1])\n",
    "result.computed_energies = np.asarray([-1])\n",
    "\n",
    "\n",
    "# now, let's interpret it\n",
    "electronic_energy.interpret(result)\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f3cb9a1",
   "metadata": {},
   "source": [
    "While this particular example is not yet very impressive, wait until we use more properties at once."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da4f9669",
   "metadata": {},
   "source": [
    "#### ParticleNumber"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2e2d67c",
   "metadata": {},
   "source": [
    "The `ParticleNumber` property also takes a special place among the builtin properties because it serves a dual purpose of:\n",
    "\n",
    "* storing the number of particles in the electronic system\n",
    "* providing the operators to evaluate the number of particles for a given eigensolution of the problem\n",
    "\n",
    "Therefore, this property is required if you want to use additional functionality like the `ActiveSpaceTransformer` or the `ElectronicStructureProblem.default_filter_criterion()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "ef9902bb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ParticleNumber:\n",
      "\t4 SOs\n",
      "\t1 alpha electrons\n",
      "\t\torbital occupation: [1. 0.]\n",
      "\t1 beta electrons\n",
      "\t\torbital occupation: [1. 0.]\n"
     ]
    }
   ],
   "source": [
    "particle_number = ParticleNumber(\n",
    "    num_spin_orbitals=4,\n",
    "    num_particles=(1, 1),\n",
    ")\n",
    "print(particle_number)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db09179e",
   "metadata": {},
   "source": [
    "#### GroupedProperty"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3da94c12",
   "metadata": {},
   "source": [
    "Rather than iterating all of the other properties one by one, let us simply consider a group of properties as provided by any `ElectronicStructureDriver` from the `qiskit_nature.drivers.second_quantization` module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "2bc19d52",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.drivers.second_quantization.pyscfd import PySCFDriver"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "bfbfd64a",
   "metadata": {},
   "outputs": [],
   "source": [
    "electronic_driver = PySCFDriver(atom=\"H 0 0 0; H 0 0 0.735\", basis=\"sto3g\")\n",
    "electronic_driver_result = electronic_driver.run()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "5565cdc8",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ElectronicStructureDriverResult:\n",
      "\tDriverMetadata:\n",
      "\t\tProgram: PYSCF\n",
      "\t\tVersion: 1.7.6\n",
      "\t\tConfig:\n",
      "\t\t\tatom=H 0 0 0; H 0 0 0.735\n",
      "\t\t\tunit=Angstrom\n",
      "\t\t\tcharge=0\n",
      "\t\t\tspin=0\n",
      "\t\t\tbasis=sto3g\n",
      "\t\t\tmethod=rhf\n",
      "\t\t\tconv_tol=1e-09\n",
      "\t\t\tmax_cycle=50\n",
      "\t\t\tinit_guess=minao\n",
      "\t\t\tmax_memory=4000\n",
      "\t\t\t\n",
      "\tElectronicBasisTransform:\n",
      "\t\tInitial basis: atomic\n",
      "\t\tFinal basis: molecular\n",
      "\t\tAlpha coefficients:\n",
      "\t\t[0, 0] = 0.5483020229014736\n",
      "\t\t[0, 1] = -1.2183273138546826\n",
      "\t\t[1, 0] = 0.548302022901473\n",
      "\t\t[1, 1] = 1.2183273138546828\n",
      "\t\tBeta coefficients:\n",
      "\t\t[0, 0] = 0.5483020229014736\n",
      "\t\t[0, 1] = -1.2183273138546826\n",
      "\t\t[1, 0] = 0.548302022901473\n",
      "\t\t[1, 1] = 1.2183273138546828\n",
      "\tParticleNumber:\n",
      "\t\t4 SOs\n",
      "\t\t1 alpha electrons\n",
      "\t\t\torbital occupation: [1. 0.]\n",
      "\t\t1 beta electrons\n",
      "\t\t\torbital occupation: [1. 0.]\n",
      "\tElectronicEnergy\n",
      "\t\t(AO) 1-Body Terms:\n",
      "\t\t\tAlpha\n",
      "\t\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t\t[0, 0] = -1.1242175791954514\n",
      "\t\t\t[0, 1] = -0.9652573993472758\n",
      "\t\t\t[1, 0] = -0.9652573993472758\n",
      "\t\t\t[1, 1] = -1.1242175791954512\n",
      "\t\t\tBeta\n",
      "\t\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t\t[0, 0] = -1.1242175791954514\n",
      "\t\t\t[0, 1] = -0.9652573993472758\n",
      "\t\t\t[1, 0] = -0.9652573993472758\n",
      "\t\t\t[1, 1] = -1.1242175791954512\n",
      "\t\t(AO) 2-Body Terms:\n",
      "\t\t\tAlpha-Alpha\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.7746059439198978\n",
      "\t\t\t[0, 0, 0, 1] = 0.4474457245330949\n",
      "\t\t\t[0, 0, 1, 0] = 0.447445724533095\n",
      "\t\t\t[0, 0, 1, 1] = 0.5718769760004512\n",
      "\t\t\t[0, 1, 0, 0] = 0.4474457245330951\n",
      "\t\t\t... skipping 11 entries\n",
      "\t\t\tBeta-Alpha\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.7746059439198978\n",
      "\t\t\t[0, 0, 0, 1] = 0.4474457245330949\n",
      "\t\t\t[0, 0, 1, 0] = 0.447445724533095\n",
      "\t\t\t[0, 0, 1, 1] = 0.5718769760004512\n",
      "\t\t\t[0, 1, 0, 0] = 0.4474457245330951\n",
      "\t\t\t... skipping 11 entries\n",
      "\t\t\tBeta-Beta\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.7746059439198978\n",
      "\t\t\t[0, 0, 0, 1] = 0.4474457245330949\n",
      "\t\t\t[0, 0, 1, 0] = 0.447445724533095\n",
      "\t\t\t[0, 0, 1, 1] = 0.5718769760004512\n",
      "\t\t\t[0, 1, 0, 0] = 0.4474457245330951\n",
      "\t\t\t... skipping 11 entries\n",
      "\t\t\tAlpha-Beta\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.7746059439198978\n",
      "\t\t\t[0, 0, 0, 1] = 0.4474457245330949\n",
      "\t\t\t[0, 0, 1, 0] = 0.447445724533095\n",
      "\t\t\t[0, 0, 1, 1] = 0.5718769760004512\n",
      "\t\t\t[0, 1, 0, 0] = 0.4474457245330951\n",
      "\t\t\t... skipping 11 entries\n",
      "\t\t(MO) 1-Body Terms:\n",
      "\t\t\tAlpha\n",
      "\t\t\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t\t\t[0, 0] = -1.2563390730032507\n",
      "\t\t\t[1, 1] = -0.47189600728114073\n",
      "\t\t\tBeta\n",
      "\t\t\t<(2, 2) matrix with 2 non-zero entries>\n",
      "\t\t\t[0, 0] = -1.2563390730032507\n",
      "\t\t\t[1, 1] = -0.47189600728114073\n",
      "\t\t(MO) 2-Body Terms:\n",
      "\t\t\tAlpha-Alpha\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 8 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.675710154803517\n",
      "\t\t\t[0, 0, 1, 1] = 0.6645817302552974\n",
      "\t\t\t[0, 1, 0, 1] = 0.18093119978423153\n",
      "\t\t\t[0, 1, 1, 0] = 0.18093119978423136\n",
      "\t\t\t[1, 0, 0, 1] = 0.18093119978423164\n",
      "\t\t\t... skipping 3 entries\n",
      "\t\t\tBeta-Alpha\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 8 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.675710154803517\n",
      "\t\t\t[0, 0, 1, 1] = 0.6645817302552974\n",
      "\t\t\t[0, 1, 0, 1] = 0.18093119978423153\n",
      "\t\t\t[0, 1, 1, 0] = 0.18093119978423136\n",
      "\t\t\t[1, 0, 0, 1] = 0.18093119978423164\n",
      "\t\t\t... skipping 3 entries\n",
      "\t\t\tBeta-Beta\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 8 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.675710154803517\n",
      "\t\t\t[0, 0, 1, 1] = 0.6645817302552974\n",
      "\t\t\t[0, 1, 0, 1] = 0.18093119978423153\n",
      "\t\t\t[0, 1, 1, 0] = 0.18093119978423136\n",
      "\t\t\t[1, 0, 0, 1] = 0.18093119978423164\n",
      "\t\t\t... skipping 3 entries\n",
      "\t\t\tAlpha-Beta\n",
      "\t\t\t<(2, 2, 2, 2) matrix with 8 non-zero entries>\n",
      "\t\t\t[0, 0, 0, 0] = 0.675710154803517\n",
      "\t\t\t[0, 0, 1, 1] = 0.6645817302552974\n",
      "\t\t\t[0, 1, 0, 1] = 0.18093119978423153\n",
      "\t\t\t[0, 1, 1, 0] = 0.18093119978423136\n",
      "\t\t\t[1, 0, 0, 1] = 0.18093119978423164\n",
      "\t\t\t... skipping 3 entries\n",
      "\tElectronicDipoleMoment:\n",
      "\t\tDipoleMomentX\n",
      "\t\t\t(AO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t(MO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\tDipoleMomentY\n",
      "\t\t\t(AO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t(MO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 0 non-zero entries>\n",
      "\t\tDipoleMomentZ\n",
      "\t\t\t(AO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 3 non-zero entries>\n",
      "\t\t\t\t[0, 1] = 0.4605377079660319\n",
      "\t\t\t\t[1, 0] = 0.4605377079660319\n",
      "\t\t\t\t[1, 1] = 1.3889487015553204\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 3 non-zero entries>\n",
      "\t\t\t\t[0, 1] = 0.4605377079660319\n",
      "\t\t\t\t[1, 0] = 0.4605377079660319\n",
      "\t\t\t\t[1, 1] = 1.3889487015553204\n",
      "\t\t\t(MO) 1-Body Terms:\n",
      "\t\t\t\tAlpha\n",
      "\t\t\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t\t\t[0, 0] = 0.69447435077766\n",
      "\t\t\t\t[0, 1] = 0.9278334704592323\n",
      "\t\t\t\t[1, 0] = 0.9278334704592324\n",
      "\t\t\t\t[1, 1] = 0.6944743507776605\n",
      "\t\t\t\tBeta\n",
      "\t\t\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t\t\t[0, 0] = 0.69447435077766\n",
      "\t\t\t\t[0, 1] = 0.9278334704592323\n",
      "\t\t\t\t[1, 0] = 0.9278334704592324\n",
      "\t\t\t\t[1, 1] = 0.6944743507776605\n",
      "\tAngularMomentum:\n",
      "\t\t4 SOs\n",
      "\tMagnetization:\n",
      "\t\t4 SOs\n",
      "Molecule:\n",
      "\tMultiplicity: 1\n",
      "\tCharge: 0\n",
      "\tGeometry:\n",
      "\t\tH\t[0.0, 0.0, 0.0]\n",
      "\t\tH\t[0.0, 0.0, 1.3889487015553204]\n",
      "\tMasses:\n",
      "\t\tH\t1\n",
      "\t\tH\t1\n"
     ]
    }
   ],
   "source": [
    "print(electronic_driver_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "205a86e1",
   "metadata": {},
   "source": [
    "There is a lot going on but with the explanations above you should not have any problems with understanding this output."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1926ae75",
   "metadata": {},
   "source": [
    "#### Constructing a Hamiltonian from raw integrals"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b04d962",
   "metadata": {},
   "source": [
    "If you have obtained some raw one- and two-body integrals by means other than through a driver provided by Qiskit Nature, you can still easily construct an `ElectronicEnergy` property to serve as your access point into the stack:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "5b01fd2d",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ElectronicEnergy\n",
      "\t(MO) 1-Body Terms:\n",
      "\t\tAlpha\n",
      "\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t[0, 0] = 1.0\n",
      "\t\t[0, 1] = 2.0\n",
      "\t\t[1, 0] = 3.0\n",
      "\t\t[1, 1] = 4.0\n",
      "\t\tBeta\n",
      "\t\t<(2, 2) matrix with 4 non-zero entries>\n",
      "\t\t[0, 0] = 1.0\n",
      "\t\t[0, 1] = 2.0\n",
      "\t\t[1, 0] = 3.0\n",
      "\t\t[1, 1] = 4.0\n",
      "\t(MO) 2-Body Terms:\n",
      "\t\tAlpha-Alpha\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 1.0\n",
      "\t\t[0, 0, 0, 1] = 2.0\n",
      "\t\t[0, 0, 1, 0] = 3.0\n",
      "\t\t[0, 0, 1, 1] = 4.0\n",
      "\t\t[0, 1, 0, 0] = 5.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tBeta-Alpha\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 1.0\n",
      "\t\t[0, 0, 0, 1] = 2.0\n",
      "\t\t[0, 0, 1, 0] = 3.0\n",
      "\t\t[0, 0, 1, 1] = 4.0\n",
      "\t\t[0, 1, 0, 0] = 5.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tBeta-Beta\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 1.0\n",
      "\t\t[0, 0, 0, 1] = 2.0\n",
      "\t\t[0, 0, 1, 0] = 3.0\n",
      "\t\t[0, 0, 1, 1] = 4.0\n",
      "\t\t[0, 1, 0, 0] = 5.0\n",
      "\t\t... skipping 11 entries\n",
      "\t\tAlpha-Beta\n",
      "\t\t<(2, 2, 2, 2) matrix with 16 non-zero entries>\n",
      "\t\t[0, 0, 0, 0] = 1.0\n",
      "\t\t[0, 0, 0, 1] = 2.0\n",
      "\t\t[0, 0, 1, 0] = 3.0\n",
      "\t\t[0, 0, 1, 1] = 4.0\n",
      "\t\t[0, 1, 0, 0] = 5.0\n",
      "\t\t... skipping 11 entries\n"
     ]
    }
   ],
   "source": [
    "one_body_ints = np.arange(1, 5).reshape((2, 2))\n",
    "two_body_ints = np.arange(1, 17).reshape((2, 2, 2, 2))\n",
    "electronic_energy_from_ints = ElectronicEnergy.from_raw_integrals(\n",
    "    ElectronicBasis.MO, one_body_ints, two_body_ints\n",
    ")\n",
    "print(electronic_energy_from_ints)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "451f13a7",
   "metadata": {},
   "source": [
    "### Vibrational Second Quantization Properties"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40d49b52",
   "metadata": {},
   "source": [
    "The `vibrational` stack for vibrational structure calculations also integrates with the Property framework. After the above introduction you should be able to understand the following directly:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "cfc3533d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.drivers.second_quantization.gaussiand import GaussianForcesDriver"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "41034f80",
   "metadata": {},
   "outputs": [],
   "source": [
    "# if you ran Gaussian elsewhere and already have the output file\n",
    "vibrational_driver = GaussianForcesDriver(logfile=\"aux_files/CO2_freq_B3LYP_ccpVDZ.log\")\n",
    "vibrational_driver_result = vibrational_driver.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e56e2c9f",
   "metadata": {},
   "source": [
    "For vibrational structure calculations we always need to define the basis which we want to work in, separately:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e73da1c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_nature.properties.second_quantization.vibrational.bases import HarmonicBasis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "40581303",
   "metadata": {},
   "outputs": [],
   "source": [
    "harmonic_basis = HarmonicBasis([2] * 4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "a5ac986f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VibrationalStructureDriverResult:\n",
      "\tHarmonicBasis:\n",
      "\t\tModals: [2, 2, 2, 2]:\n",
      "\tVibrationalEnergy:\n",
      "\t\tHarmonicBasis:\n",
      "\t\t\tModals: [2, 2, 2, 2]\n",
      "\t\t1-Body Terms:\n",
      "\t\t\t<sparse integral list with 13 entries>\n",
      "\t\t\t(1, 1) = 605.3643675\n",
      "\t\t\t(-1, -1) = -605.3643675\n",
      "\t\t\t(2, 2) = 340.5950575\n",
      "\t\t\t(-2, -2) = -340.5950575\n",
      "\t\t\t(3, 3) = 163.7595125\n",
      "\t\t\t... skipping 8 entries\n",
      "\t\t2-Body Terms:\n",
      "\t\t\t<sparse integral list with 15 entries>\n",
      "\t\t\t(2, 1, 1) = -89.09086530649508\n",
      "\t\t\t(3, 3, 2) = 21.644966371722838\n",
      "\t\t\t(4, 4, 2) = 6.412754934114705\n",
      "\t\t\t(2, 2, 1, 1) = 5.03965375\n",
      "\t\t\t(3, 1, 1, 1) = -2.4473854166666666\n",
      "\t\t\t... skipping 10 entries\n",
      "\t\t3-Body Terms:\n",
      "\t\t\t<sparse integral list with 9 entries>\n",
      "\t\t\t(3, 2, 1) = 44.01468537435673\n",
      "\t\t\t(4, 2, 1) = -78.71701132125833\n",
      "\t\t\t(4, 3, 2) = 17.15529085952822\n",
      "\t\t\t(3, 2, 2, 1) = -3.73513125\n",
      "\t\t\t(4, 2, 2, 1) = 6.68000625\n",
      "\t\t\t... skipping 4 entries\n",
      "\tOccupiedModals:\n",
      "\t\tHarmonicBasis:\n",
      "\t\t\tModals: [2, 2, 2, 2]\n"
     ]
    }
   ],
   "source": [
    "vibrational_driver_result.basis = harmonic_basis\n",
    "print(vibrational_driver_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02b76a8f",
   "metadata": {},
   "source": [
    "## Writing custom Properties"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69929433",
   "metadata": {},
   "source": [
    "You can extend the Property framework with your own implementations. Here, we will walk through the simple example of constructing a Property for the _electronic density_. Since this observable is essentially based on matrices, we will be leveraging the `OneBodyElectronicIntegrals` container to store the actual density matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "36ed2d9a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import product\n",
    "from typing import List\n",
    "\n",
    "from qiskit_nature.drivers import QMolecule\n",
    "from qiskit_nature.operators.second_quantization import FermionicOp\n",
    "from qiskit_nature.properties.second_quantization.electronic.bases import ElectronicBasis\n",
    "from qiskit_nature.properties.second_quantization.electronic.types import ElectronicProperty\n",
    "from qiskit_nature.properties.second_quantization.electronic.integrals import (\n",
    "    OneBodyElectronicIntegrals,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "467bf2a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ElectronicDensity(ElectronicProperty):\n",
    "    \"\"\"A simple electronic density property.\n",
    "\n",
    "    This basic example works only in the MO basis!\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, num_molecular_orbitals: int) -> None:\n",
    "        super().__init__(self.__class__.__name__)\n",
    "        self._num_molecular_orbitals = num_molecular_orbitals\n",
    "\n",
    "    def __str__(self) -> str:\n",
    "        string = [super().__str__() + \":\"]\n",
    "        string += [f\"\\t{self._num_molecular_orbitals} MOs\"]\n",
    "        return \"\\n\".join(string)\n",
    "\n",
    "    @classmethod\n",
    "    def from_legacy_driver_result(cls, result) -> \"ElectronicDensity\":\n",
    "        cls._validate_input_type(result, QMolecule)\n",
    "\n",
    "        qmol = cast(QMolecule, result)\n",
    "\n",
    "        return cls(qmol.num_molecular_orbitals)\n",
    "\n",
    "    def second_q_ops(self) -> List[FermionicOp]:\n",
    "        ops = []\n",
    "\n",
    "        # iterate all pairs of molecular orbitals\n",
    "        for mo_i, mo_j in product(range(self._num_molecular_orbitals), repeat=2):\n",
    "\n",
    "            # construct an auxiliary matrix where the only non-zero entry is at the current pair of MOs\n",
    "            number_op_matrix = np.zeros(\n",
    "                (self._num_molecular_orbitals, self._num_molecular_orbitals)\n",
    "            )\n",
    "            number_op_matrix[mo_i, mo_j] = 1\n",
    "\n",
    "            # leverage the OneBodyElectronicIntegrals to construct the corresponding FermionicOp\n",
    "            one_body_ints = OneBodyElectronicIntegrals(\n",
    "                ElectronicBasis.MO, (number_op_matrix, number_op_matrix)\n",
    "            )\n",
    "            ops.append(one_body_ints.to_second_q_op())\n",
    "\n",
    "        return ops\n",
    "\n",
    "    def interpret(self, result) -> None:\n",
    "        # here goes the code which interprets the eigenvalues returned for the auxiliary operators\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "e3aae121",
   "metadata": {},
   "outputs": [],
   "source": [
    "density = ElectronicDensity(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "b7fcd848",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ElectronicDensity:\n",
      "\t2 MOs\n"
     ]
    }
   ],
   "source": [
    "print(density)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "27657693",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 : Fermionic Operator\n",
      "register length=4, number terms=2\n",
      "  (1+0j) * ( +_0 -_0 )\n",
      "+ (1+0j) * ( +_2 -_2 )\n",
      "1 : Fermionic Operator\n",
      "register length=4, number terms=2\n",
      "  (1+0j) * ( +_0 -_1 )\n",
      "+ (1+0j) * ( +_2 -_3 )\n",
      "2 : Fermionic Operator\n",
      "register length=4, number terms=2\n",
      "  (1+0j) * ( +_1 -_0 )\n",
      "+ (1+0j) * ( +_3 -_2 )\n",
      "3 : Fermionic Operator\n",
      "register length=4, number terms=2\n",
      "  (1+0j) * ( +_1 -_1 )\n",
      "+ (1+0j) * ( +_3 -_3 )\n"
     ]
    }
   ],
   "source": [
    "for idx, op in enumerate(density.second_q_ops()):\n",
    "    print(idx, \":\", op)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5285215",
   "metadata": {},
   "source": [
    "Of course, the above example is very minimal and can be extended at will."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7224fd20",
   "metadata": {},
   "source": [
    "**Note:** as of Qiskit Nature version 0.2.0, the direct integration of custom Property objects into the stack is not implemented yet, due to limitations of the auxiliary operator parsing. See https://github.com/Qiskit/qiskit-nature/issues/312 for more details.\n",
    "\n",
    "For the time being, you can still evaluate a custom Property, by passing it's generated operators directly to the `Eigensolver.solve` method by means of constructing the `aux_operators`. Note, however, that you will have to deal with transformations applied to your properties manually, until the above issue is resolved."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "e5b97870",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set up some problem\n",
    "problem = ...\n",
    "# set up a solver\n",
    "solver = ...\n",
    "# when solving the problem, pass additional operators in like so:\n",
    "aux_ops = density.second_q_ops()\n",
    "# solver.solve(problem, aux_ops)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "1fcbe2a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<h3>Version Information</h3><table><tr><th>Qiskit Software</th><th>Version</th></tr><tr><td><code>qiskit-terra</code></td><td>0.19.0.dev0+be0ed5f</td></tr><tr><td><code>qiskit-aer</code></td><td>0.9.0</td></tr><tr><td><code>qiskit-ignis</code></td><td>0.7.0.dev0+9201ed8</td></tr><tr><td><code>qiskit-ibmq-provider</code></td><td>0.16.0.dev0+4f6b7f6</td></tr><tr><td><code>qiskit-aqua</code></td><td>0.9.0.dev0+81239d0</td></tr><tr><td><code>qiskit-nature</code></td><td>0.2.0</td></tr><tr><th>System information</th></tr><tr><td>Python</td><td>3.9.6 (default, Jul 16 2021, 00:00:00) \n",
       "[GCC 10.3.1 20210422 (Red Hat 10.3.1-1)]</td></tr><tr><td>OS</td><td>Linux</td></tr><tr><td>CPUs</td><td>4</td></tr><tr><td>Memory (Gb)</td><td>14.842548370361328</td></tr><tr><td colspan='2'>Wed Aug 18 09:35:15 2021 CEST</td></tr></table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div style='width: 100%; background-color:#d5d9e0;padding-left: 10px; padding-bottom: 10px; padding-right: 10px; padding-top: 5px'><h3>This code is a part of Qiskit</h3><p>&copy; Copyright IBM 2017, 2021.</p><p>This code is licensed under the Apache License, Version 2.0. You may<br>obtain a copy of this license in the LICENSE.txt file in the root directory<br> of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.<p>Any modifications or derivative works of this code must retain this<br>copyright notice, and modified files need to carry a notice indicating<br>that they have been altered from the originals.</p></div>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import qiskit.tools.jupyter\n",
    "\n",
    "%qiskit_version_table\n",
    "%qiskit_copyright"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
